﻿using System.Collections.Generic;
using System.Linq;
using Notoric.Commanding.CommandBase;

namespace Notoric.ViewModel
{
    /// <summary>
    /// An undoable command for creating a slur (legato) connecting two chords.
    /// </summary>
    public class SlurChordsCommand : UndoableCommandBase
    {
        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="SlurChordsCommand"/> class.
        /// </summary>
        /// <param name="undoStack">The undo command stack</param>
        /// <param name="redoStack">The redo command stack</param>
        public SlurChordsCommand(CommandStack undoStack, CommandStack redoStack)
            : base(undoStack, redoStack)
        {
            AddParameter<Elements.CWNotation.Chord>("Start", ParameterDirection.In);
            AddParameter<Elements.CWNotation.Chord>("End", ParameterDirection.In);

            Name = "Create Slur";
            createdSlurs = new List<Elements.CWNotation.Slur>();
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Processes one affected section.
        /// </summary>
        /// <param name="section">
        /// <para>
        /// Type: <see cref="Elements.MusicSection"/>
        /// </para>
        /// <para>
        /// The section.
        /// </para>
        /// </param>
        /// <param name="state">
        /// <para>
        /// Type: <see cref="Commanding.CommandBase.ExecutionState"/>
        /// </para>
        /// <para>
        /// The current execution state of the command.
        /// </para>
        /// </param>
        private void ProcessAffectedSection(Elements.MusicSection section, ExecutionState state)
        {
            var modelSection = section.AdaptedElement as Model.Elements.MusicSectionElement;

            if (!modelSection.Parts.Contains(Start.AdaptedElement.FindOwner<Model.Elements.CWNotation.Staff>().Descriptor.FindOwner<Model.Elements.PartDescriptorElement>()))
                return;

            var modelStartMeasure = ((Model.Elements.CWNotation.Chord)Start.AdaptedElement).Measure;
            var modelEndMeasure = ((Model.Elements.CWNotation.Chord)End.AdaptedElement).Measure;
            var modelInterior = modelSection.Interior as Model.Elements.CWNotation.MusicSectionInterior;
            var modelStartSystem = modelInterior.Systems.FirstOrDefault(s => s.Measures.Contains(modelStartMeasure));
            var modelEndSystem = modelInterior.Systems.FirstOrDefault(s => s.Measures.Contains(modelEndMeasure));
            if (modelStartSystem == null)
                modelStartSystem = modelInterior.Systems[0];
            if (modelEndSystem == null)
                modelEndSystem = modelInterior.Systems[modelInterior.Systems.Count - 1];

            var systems = new List<Elements.CWNotation.System>();
            var interior = section.Interior as Elements.CWNotation.MusicSectionInterior;
            systems.Add(interior.Systems.First(s => s.AdaptedElement == modelStartSystem));
            if (modelEndSystem != modelStartSystem)
                systems.Add(interior.Systems.First(s => s.AdaptedElement == modelEndSystem));

            foreach (var system in systems)
            {
                if (state == Commanding.CommandBase.ExecutionState.NotExecuted)
                {
                    createdSlurs.Add(
                        system.Connectors.First(c => c.AdaptedElement is Model.Elements.CWNotation.Slur && ((Model.Elements.CWNotation.Slur)c.AdaptedElement).PresentedConnector == createdConnector) as Elements.CWNotation.Slur
                        );
                }
                else if (ExecutionState == Commanding.CommandBase.ExecutionState.Undone)
                {
                    var vmConnector = system.Connectors.First(c => c.AdaptedElement is Model.Elements.CWNotation.Slur && ((Model.Elements.CWNotation.Slur)c.AdaptedElement).PresentedConnector == createdConnector);
                    var mySlur = createdSlurs.First(s => s.AdaptedElement == vmConnector.AdaptedElement);
                    var modelSystem = system.AdaptedElement as Model.Elements.CWNotation.System;
                    modelSystem.RestoreConnector(mySlur.AdaptedElement as Model.Elements.CWNotation.Connector);
                    system.Connectors[system.Connectors.IndexOf(vmConnector)] = mySlur;
                }
                if (section.FindOwner<Elements.Page>() != null)
                    Model.Elements.CWNotation.Layout.Layouter.LayoutHorizontally(system.AdaptedElement as Model.Elements.CWNotation.System);
                else
                    Model.Elements.CWNotation.Layout.Layouter.LayoutHorizontallyNA(system.AdaptedElement as Model.Elements.CWNotation.System);
            }
        }

        #endregion

        #region Command Logic

        /// <inheritdoc/>
        protected override void CommandOperation(object parameter)
        {
            var modelStart = ((Model.Elements.CWNotation.Chord)Start.AdaptedElement).PresentedElement as Model.Elements.ChordElement;
            var modelEnd = ((Model.Elements.CWNotation.Chord)End.AdaptedElement).PresentedElement as Model.Elements.ChordElement;
            var composition = modelStart.FindOwner<Model.Elements.CompositionElement>();

            createdConnector = composition.AddLegato(modelStart, modelEnd);
            var references = composition.GetReferencingPages(modelStart.FindOwner<Model.Elements.MeasureElement>(), modelEnd.FindOwner<Model.Elements.MeasureElement>());
            var project = Start.FindOwner<Elements.Project>();
            foreach (var reference in references)
            {
                var score = project.Scores.First(s => s.AdaptedPart == reference.Page.Owner);
                var page = score.Pages.First(p => p.AdaptedPart == reference.Page);
                var section = page.Sections.First(s => s.AdaptedElement == reference.Section) as Elements.MusicSection;

                ProcessAffectedSection(section, Commanding.CommandBase.ExecutionState.NotExecuted);
            }

            var vmComposition = project.Compositions.First(c => c.AdaptedElement == composition);
            ProcessAffectedSection(vmComposition.LinearView.Content, Commanding.CommandBase.ExecutionState.NotExecuted);

            project.IsModified = true;
        }

        /// <inheritdoc/>
        protected override void RedoOperation()
        {
            var modelStart = ((Model.Elements.CWNotation.Chord)Start.AdaptedElement).PresentedElement as Model.Elements.ChordElement;
            var modelEnd = ((Model.Elements.CWNotation.Chord)End.AdaptedElement).PresentedElement as Model.Elements.ChordElement;
            var composition = modelStart.FindOwner<Model.Elements.CompositionElement>();

            composition.Connectors.Add(createdConnector);
            var references = composition.GetReferencingPages(modelStart.FindOwner<Model.Elements.MeasureElement>(), modelEnd.FindOwner<Model.Elements.MeasureElement>());
            var project = Start.FindOwner<Elements.Project>();
            foreach (var reference in references)
            {
                var score = project.Scores.First(s => s.AdaptedPart == reference.Page.Owner);
                var page = score.Pages.First(p => p.AdaptedPart == reference.Page);
                var section = page.Sections.First(s => s.AdaptedElement == reference.Section) as Elements.MusicSection;

                ProcessAffectedSection(section, Commanding.CommandBase.ExecutionState.Undone);
            }

            var vmComposition = project.Compositions.First(c => c.AdaptedElement == composition);
            ProcessAffectedSection(vmComposition.LinearView.Content, Commanding.CommandBase.ExecutionState.Undone);

            project.IsModified = true;
        }

        /// <inheritdoc/>
        protected override void UndoOperation()
        {
            var modelStart = ((Model.Elements.CWNotation.Chord)Start.AdaptedElement).PresentedElement as Model.Elements.ChordElement;
            var modelEnd = ((Model.Elements.CWNotation.Chord)End.AdaptedElement).PresentedElement as Model.Elements.ChordElement;
            var composition = modelStart.FindOwner<Model.Elements.CompositionElement>();

            composition.Connectors.Remove(createdConnector);
            var references = composition.GetReferencingPages(modelStart.FindOwner<Model.Elements.MeasureElement>(), modelEnd.FindOwner<Model.Elements.MeasureElement>());
            var project = Start.FindOwner<Elements.Project>();
            foreach (var reference in references)
            {
                var score = project.Scores.First(s => s.AdaptedPart == reference.Page.Owner);
                var page = score.Pages.First(p => p.AdaptedPart == reference.Page);
                var section = page.Sections.First(s => s.AdaptedElement == reference.Section) as Elements.MusicSection;

                ProcessAffectedSection(section, Commanding.CommandBase.ExecutionState.Executed);
            }

            var vmComposition = project.Compositions.First(c => c.AdaptedElement == composition);
            ProcessAffectedSection(vmComposition.LinearView.Content, Commanding.CommandBase.ExecutionState.Executed);

            project.IsModified = true;
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets or sets the last chord in the slur.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Elements.CWNotation.Chord"/>
        /// </para>
        /// <para>
        /// The last chord in the slur.<br/>
        /// This property wraps the "End" command parameter.
        /// </para>
        /// </value>
        public Elements.CWNotation.Chord End
        {
            get { return GetParameterValue<Elements.CWNotation.Chord>("End"); }
            set { SetParameterValue<Elements.CWNotation.Chord>("End", value); }
        }

        /// <summary>
        /// Gets or sets the first chord in the slur.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Elements.CWNotation.Chord"/>
        /// </para>
        /// <para>
        /// The first chord in the slur.<br/>
        /// This property wraps the "Start" command parameter.
        /// </para>
        /// </value>
        public Elements.CWNotation.Chord Start
        {
            get { return GetParameterValue<Elements.CWNotation.Chord>("Start"); }
            set { SetParameterValue<Elements.CWNotation.Chord>("Start", value); }
        }

        #endregion

        #region Private Fields

        /// <summary>
        /// Holds the created connector element.
        /// </summary>
        private Model.Elements.ConnectorElement createdConnector;
        /// <summary>
        /// Holds the list of created slurs.
        /// </summary>
        private List<Elements.CWNotation.Slur> createdSlurs;

        #endregion
    }
}
